﻿using SuperX.Common;
using SuperX.Common.Abstract;
using SuperX.Common.BasicService;
using SuperX.Common.Helper;
using SuperX.Common.Log;
using SuperX.Common.Object;
using SuperX.DataService.Helper;
using HslCommunication.LogNet;
using Newtonsoft.Json;
using NPOI.HSSF.Record.Chart;
using NPOI.SS.Formula.Functions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Numerics;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace SuperX.DataService.Abstract
{
    public abstract class IOTag<T> : BasicObject, IComparable<IOTag<T>>, IIOTag
    {
        private object _engineerValue;
        private object writeLock = new object();
        private const string _thisDevice = "$this.device$";
        private const string _thisGroup = "$this.group$";
        private const string _thisTag = "$this.tag$";
        private const string _thisValue = "$this.value$";

        #region Property and Field

        public override string ServiceType
        {
            get => typeof(IOTag<T>).FullName + "," + typeof(IOTag<T>).Assembly.ManifestModule.Name;
            set { }
        }

        protected bool _active = true;

        /// <summary>
        /// 是否激活
        /// </summary>
        public bool Active
        {
            get
            {
                return _active;
            }
            set
            {
                _group.SetActiveState(value, Identity);
                _active = value;
            }
        }

        protected QUALITIES _quality;

        /// <summary>
        /// 质量戳
        /// </summary>
        public QUALITIES Quality
        {
            get
            {
                return _quality;
            }
            set { _quality = value; }
        }

        protected QUALITIES _writeQuality;

        /// <summary>
        /// 写入质量戳
        /// </summary>
        public QUALITIES WriteQuality
        {
            get => _writeQuality;
            set { _writeQuality = value; }
        }

        protected T _value;

        /// <summary>
        /// 值
        /// </summary>
        public virtual T Value
        {
            get => LimitValue();
            set => WriteTagValue(value);
        }

        public TagConvertType ConverterType { get; set; }

        /// <summary>
        /// 转换比例值
        /// </summary>
        public float ScaleValue { get; set; }

        /// <summary>
        /// 写入值
        /// </summary>
        public T WriteValue
        {
            get => _writeValue;
            set => _writeValue = value;
        }

        protected T _writeValue;

        protected DateTime _timeStamp = DateTime.MinValue;

        /// <summary>
        /// 时间戳
        /// </summary>
        public DateTime TimeStamp
        {
            get
            {
                return _timeStamp;
            }
            set
            {
                _timeStamp = value;
            }
        }

        protected DeviceAddress _plcAddress;

        /// <summary>
        /// 地址
        /// </summary>
        public DeviceAddress Address
        {
            get
            {
                return _plcAddress;
            }
            set
            {
                _plcAddress = value;
            }
        }

        protected IGroup _group;

        [JsonIgnore]
        public IGroup ParentGroup
        {
            get
            {
                return _group;
            }
        }

        public TagType TagType { get; set; }
        object ITag.DefaultValue { get; set; }

        bool ITag.IsReadOnly { get; set; }
        Type ITag.ValueType { get; set; }

        public HDASetting HDASetting { get; set; }

        /// <summary>
        /// 是否存档
        /// </summary>
        public bool Archive { get; set; }

        /// <summary>
        /// 间隔触发值变化
        /// </summary>
        public double Interval { get; set; }

        /// <summary>
        /// 主键
        /// </summary>
        public string Key
        {
            get => this.FullName;
        }

        /// <summary>
        /// 读写模式
        /// </summary>
        public ReadWriteMode ReadWriteMode { get; set; }

        /// <summary>
        /// 脚本
        /// </summary>
        public string Script
        {
            get
            {
                var a = (string.IsNullOrEmpty(_script) ? _script : ExpressConvert(_script));
                return a;
            }
            set { _script = value; }
        }

        private int myVar;

        public int MyProperty
        {
            get { return myVar; }
            set { myVar = value; }
        }

        private string _script;

        /// <summary>
        /// 写入时的转换
        /// </summary>
        public string WriteScript { get; set; }

        public virtual long Maximum { get; set; }

        public virtual long Minimum { get; set; }

        public string Regester { get; set; }

        public string No { get; set; }

        public string Unit { get; set; }

        [JsonIgnore]
        public object RawValue
        {
            get
            {
                if (_engineerValue == null && Common.Helper.PythonScriptHelper.IsScriptEditMode) return default(T);
                return _engineerValue;
            }
            set
            {
                _engineerValue = value;
            }
        }

        [JsonIgnore]
        public virtual Type ValueType { get; set; } = typeof(T);

        public object InitValue { get => _initValue; set => _initValue = value; }
        private object _initValue = default(T);

        [JsonIgnore]
        object ITag.Value
        {
            get => Value;
            set => WriteTagValue(value);
        }

        object IIOTag.WriteValue { get => WriteValue; set { } }

        #endregion Property and Field

        #region constructor

        protected IOTag(DeviceAddress address, IGroup group)
        {
            _group = group;
            _plcAddress = address;
        }

        #endregion constructor

        #region 时间变化事件

        public event EventHandler<TagChangedEventArgs> TimeTriggered;

        /// <summary>
        /// 主动触发时间事件
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <param name="quality"></param>
        public void Update(DateTime timeStamp, QUALITIES quality = QUALITIES.OK)
        {
            if (_timeStamp > timeStamp) return;//如果时间戳更旧或值未改变
            _timeStamp = timeStamp;
            _quality = quality;
            if (quality == QUALITIES.OK)
            {
                if (TimeTriggered != null)
                {
                    TimeTriggered(this, new TagChangedEventArgs(this));
                }
            }
        }

        #endregion 时间变化事件

        /// <summary>
        /// 执行脚本转换
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="returnValue"></param>
        /// <returns></returns>
        public virtual bool ExecuteExpression(string expression, out T returnValue)
        {
            try
            {
                if (this.FullName.Contains("打扁")) Logger.Trace($"param2 {this.FullName} excute:{Script}");
                var project = BasicServiceManager.GetProjService().Project;
                var result = project.CodeManager.Execute(Script, project);
                if (result is T t)
                {
                    returnValue = t;
                    return true;
                }
                var value = default(T);
                if (result is BigInteger bigInteger)
                {
                    Cast(bigInteger.ToString(), out value);
                }
                else Cast(result, out value);
                returnValue = value;
                return true;
            }
            catch (Exception ex)
            {
                Logger.Warn(string.Format("Tag {0} execute script converter fail: {1}{2}", FullName, Environment.NewLine, ex.Message));
                returnValue = default(T);
                return false;
            }
        }

        /// <summary>
        /// 替换里面关键字
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        private string ExpressConvert(string expression)
        {
            var group = _group as IBasicObject;
            expression = expression.Contains(_thisValue) ? expression.Replace(_thisValue, RawValue.ToString()) : expression;
            expression = expression
                .Replace(_thisGroup, group.FullName)
                .Replace(_thisTag, FullName)
                .Replace(_thisDevice, group.Parent.FullName);
            return expression;
        }

        public virtual void ExecuteExpression()
        {
            T convertRes = default(T);
            try
            {
                if (this.FullName.Contains("打扁") && this.FullName.Contains("AF-0052")) Logger.Trace($"param0 {this.FullName} excute:{Script}");
                var project = BasicServiceManager.GetProjService().Project;
                var result = project.CodeManager.Execute(Script, project);
                if (result is T t)
                {
                    convertRes = t;
                }
                var value = default(T);
                if (result is BigInteger bigInteger)
                {
                    Cast(bigInteger.ToString(), out value);
                }
                else Cast(result, out value);
                convertRes = value;
                UpdateTagValue(convertRes);
            }
            catch (Exception ex)
            {
                Logger.Warn(string.Format("Tag {0} execute script converter fail: {1}{2}", FullName, Environment.NewLine, ex.Message));
            }
        }

        public override int GetHashCode()
        {
            return Identity.GetHashCode();
        }

        public abstract T GetValue();

        public abstract T GetWriteValue();

        #region Event

        public event EventHandler<TagChangingEventArgs> ValueChanging;

        public event EventHandler<TagChangedEventArgs> ValueChanged;

        public bool OnValueChanging(TagChangingEventArgs args)
        {
            ValueChanging?.Invoke(this, args);
            return args.Cancel;
        }

        public void OnValueChanged(TagChangedEventArgs args)
        {
            ValueChanged?.Invoke(this, args);
            if (args.EventType == ValueChangedType.ValueChanged || args.EventType == ValueChangedType.UpdateValueChanged)
            {
            }
        }

        #endregion Event

        public int CompareTo(IOTag<T> other)
        {
            if (other == null) return 1;
            return _plcAddress.CompareTo(other._plcAddress);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="rawValue"></param>
        /// <param name="tagValue"></param>
        /// <returns></returns>
        public virtual bool SetValue(T rawValue, out T tagValue)
        {
            tagValue = rawValue;
            return true;
        }

        /// <summary>
        /// 设置原始值
        /// </summary>
        /// <param name="tagValue"></param>
        /// <param name="rawValue"></param>
        /// <returns></returns>
        public virtual bool SetRawValue(T tagValue, out T rawValue)
        {
            rawValue = tagValue;
            return true;
        }

        #region 采集值变化

        public event EventHandler<TagChangedEventArgs> RawValueChanged;

        public void OnRawValueChanged(TagChangedEventArgs args)
        {
            try
            {
                RawValueChanged?.Invoke(this, args);
            }
            catch
            {
            }
        }

        #endregion 采集值变化

        #region Read and Update

        /// <summary>
        /// 采集更新，更新Tag的值，并根据情况触发值变化事件
        /// </summary>
        /// <param name="value"></param>
        /// <param name="qualityStamp"></param>
        /// <param name="timeStamp"></param>
        /// <param name="updateSourceName"></param>
        public virtual void UpdateTagValue(T value, QUALITIES qualityStamp, DateTime timeStamp, string updateSourceName)
        {
            if (!IsValidValue(value, out var actualValue))
            {
                UpdateQAndS(timeStamp, qualityStamp);
                return;
            }
            if (OnValueChanging(new TagChangingEventArgs(this)))
            {
                UpdateQAndS(timeStamp, qualityStamp);
                return;
            }
            if (!EqualityComparer<T>.Default.Equals(_value, actualValue))
            {
                var oldValue = _value;
                _value = actualValue;
                _quality = qualityStamp;
                TimeStamp = timeStamp;
                OnValueChanged(new TagChangedEventArgs(this, ValueChangedType.UpdateValueChanged, oldValue, _value, updateSourceName));
            }
            else
            {
                UpdateQAndS(timeStamp, qualityStamp);
            }
        }

        /// <summary>
        /// 只更新质量戳和时间戳
        /// </summary>
        /// <param name="timeStamp"></param>
        /// <param name="qualityStamp"></param>
        public virtual void UpdateQAndS(DateTime timeStamp, QUALITIES qualityStamp)
        {
            TimeStamp = timeStamp;
            if (Quality == qualityStamp)
                return;
            Quality = qualityStamp;
        }

        /// <summary>
        /// 更新值
        /// </summary>
        /// <param name="value"></param>
        /// <param name="qualityStamp"></param>
        /// <param name="timeStamp"></param>
        /// <param name="updateSourceName"></param>
        public void UpdateTagValue(object value, QUALITIES qualityStamp, DateTime timeStamp, string updateSourceName)
        {
            if (Cast(value, out var v))
            {
                UpdateTagValue(v, qualityStamp, timeStamp, updateSourceName);
            }
        }

        public void UpdateTagValue(object rawValue)
        {
            UpdateIoTagValue(rawValue, QUALITIES.OK, DateTime.Now);
        }

        /// <summary>
        ///更新采集值，若无转换直接赋值Value
        /// </summary>
        /// <param name="rawValue"></param>
        /// <param name="qualityStamp"></param>
        /// <param name="timeStamp"></param>
        public void UpdateIoTagValue(object rawValue, QUALITIES qualityStamp, DateTime timeStamp)
        {
            var oldValue = RawValue;
            RawValue = rawValue; //Io读取值
            OnRawValueChanged(new TagChangedEventArgs(this, ValueChangedType.UpdateValueChanged, oldValue, RawValue, FullName));
            if (!Cast(rawValue, out var v))
            {
                return;
            }
            var flag = SetValue(v, out var tagValue);
            UpdateTagValue(tagValue, flag ? qualityStamp : QUALITIES.NG, timeStamp, FullName);
        }

        public void Update(object newvalue, DateTime timeStamp, QUALITIES quality)
        {
            Update(timeStamp, quality);
        }

        #endregion Read and Update

        #region Write

        public abstract int Write(T value);

        /// <summary>
        /// 写入值
        /// </summary>
        /// <param name="value"></param>
        public void WriteTagValue(T value)
        {
            WriteTagValue(value, Quality, DateTime.Now);
        }

        /// <summary>
        /// 写入值
        /// </summary>
        /// <param name="value"></param>
        public void WriteTagValue(object value)
        {
            if (ReadWriteMode == ReadWriteMode.ReadOnly)
                return;
            if (Cast(value, out var v))
            {
                WriteTagValue(v);
            }
        }

        /// <summary>
        /// 写入值,前端->到writevalue->到设备
        /// </summary>
        /// <param name="value"></param>
        /// <param name="qualityStamp"></param>
        /// <param name="timeStamp"></param>
        public virtual void WriteTagValue(T value, QUALITIES qualityStamp, DateTime timeStamp)
        {
            if (ReadWriteMode == ReadWriteMode.ReadOnly || TagType == TagType.System || !IsValidValue(value, out var actualValue))
            {
                return;
            }
            lock (writeLock)
            {
            }
        }

        public void WriteTagValue(object value, QUALITIES qualityStamp, DateTime timeStamp)
        {
            if (ReadWriteMode == ReadWriteMode.ReadOnly)
                return;
            if (Cast(value, out var v))
            {
                WriteTagValue(v, qualityStamp, timeStamp);
            }
        }

        #endregion Write

        /// <summary>
        /// 判断值有效性
        /// </summary>
        /// <param name="value"></param>
        /// <param name="actualValue"></param>
        /// <returns></returns>
        public virtual bool IsValidValue(T value, out T actualValue)
        {
            actualValue = value;
            return Value == null || !Value.Equals(actualValue);
        }

        /// <summary>
        /// object类型的value根据泛型转换
        /// </summary>
        /// <param name="value"></param>
        /// <param name="castValue"></param>
        /// <returns></returns>
        public bool Cast(object value, out T castValue)
        {
            try
            {
                castValue = (T)Convert.ChangeType(value, ValueType);
                return true;
            }
            catch (Exception)
            {
                castValue = default(T);
                return false;
            }
        }

        public abstract T LimitValue();
    }

    public sealed class BoolTag : IOTag<bool>
    {
        public BoolTag(DeviceAddress addr, IGroup group)
            : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(bool value)
        {
            return _group.WriteBit(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override bool GetValue()
        {
            return _value;
        }

        public override bool GetWriteValue()
        {
            return _writeValue;
        }

        public override bool LimitValue()
        {
            return _value;
        }

        public override bool SetValue(bool rawValue, out bool tagValue)
        {
            tagValue = Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Expression:
                    bool result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                default:
                    tagValue = rawValue;
                    break;
            }
            return true;
        }

        public override bool SetRawValue(bool tagValue, out bool rawValue)
        {
            Cast(RawValue, out rawValue);
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Expression:
                    bool result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class ByteTag : IOTag<byte>
    {
        public ByteTag(DeviceAddress addr, IGroup group)
            : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(byte value)
        {
            return _group.WriteBits(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override byte GetValue()
        {
            return _value;
        }

        public override byte GetWriteValue()
        {
            return _writeValue;
        }

        public override byte LimitValue()
        {
            var max = Maximum > byte.MaxValue ? byte.MaxValue : (byte)Maximum;
            var min = Minimum < byte.MinValue ? byte.MinValue : (byte)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(byte rawValue, out byte tagValue)
        {
            tagValue = (byte)Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    tagValue = (Byte)(ScaleValue * rawValue);
                    break;

                case TagConvertType.Expression:
                    byte result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                case TagConvertType.BCD:
                    var temp = rawValue.ToString("X");
                    tagValue = temp.ObjToByte();
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(byte tagValue, out byte rawValue)
        {
            Cast(RawValue, out rawValue);
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (Byte)(ScaleValue * tagValue);
                    break;

                case TagConvertType.Expression:
                    byte result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                case TagConvertType.BCD:
                    {
                        var temp = tagValue.ToString("X");
                        rawValue = temp.ObjToByte();
                        break;
                    }

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class ShortTag : IOTag<short>
    {
        public ShortTag(DeviceAddress addr, IGroup group)
            : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(short value)
        {
            return _group.WriteInt16(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override short GetValue()
        {
            return _value;
        }

        public override short GetWriteValue()
        {
            return _writeValue;
        }

        public override short LimitValue()
        {
            var max = Maximum > short.MaxValue ? short.MaxValue : (short)Maximum;
            var min = Minimum < short.MinValue ? short.MinValue : (short)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(Int16 rawValue, out Int16 tagValue)
        {
            tagValue = Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    var temp = (float)rawValue * ScaleValue;
                    tagValue = ((float)rawValue * ScaleValue).ObjToShort();
                    break;

                case TagConvertType.Expression:
                    Int16 result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                case TagConvertType.BCD:
                    tagValue = rawValue.ToString("X").ObjToShort();
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(Int16 tagValue, out Int16 rawValue)
        {
            Cast(RawValue, out rawValue);
            //engineerValue = (EngineerValue != null) ? (Int16)EngineerValue : (Int16)0;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (Int16)(tagValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    Int16 result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                case TagConvertType.BCD:
                    rawValue = tagValue.ToString("X").ObjToShort();
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class UShortTag : IOTag<ushort>
    {
        public UShortTag(DeviceAddress addr, IGroup group) : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(ushort value)
        {
            return _group.WriteUInt16(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override ushort GetValue()
        {
            return _value;
        }

        public override ushort GetWriteValue()
        {
            return _writeValue;
        }

        public override ushort LimitValue()
        {
            var max = Maximum > ushort.MaxValue ? ushort.MaxValue : (ushort)Maximum;
            var min = Minimum < ushort.MinValue ? ushort.MinValue : (ushort)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(UInt16 rawValue, out UInt16 tagValue)
        {
            tagValue = (UInt16)Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    tagValue = (UInt16)(ScaleValue * rawValue);
                    break;

                case TagConvertType.Expression:
                    UInt16 result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                case TagConvertType.BCD:
                    tagValue = rawValue.ToString("X").ObjToUshort();
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(UInt16 tagValue, out UInt16 rawValue)
        {
            Cast(RawValue, out rawValue);
            rawValue = tagValue;
            //engineerValue = (EngineerValue != null) ? (UInt16)EngineerValue : (UInt16)0;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (UInt16)(tagValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    UInt16 result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                case TagConvertType.BCD:
                    rawValue = tagValue.ToString("X").ObjToUshort();
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class IntTag : IOTag<int>
    {
        public IntTag(DeviceAddress addr, IGroup group) : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(int value)
        {
            return _group.WriteInt32(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override int GetValue()
        {
            return _value;
        }

        public override int GetWriteValue()
        {
            return _writeValue;
        }

        public override int LimitValue()
        {
            var max = Maximum > int.MaxValue ? int.MaxValue : (int)Maximum;
            var min = Minimum < int.MinValue ? int.MinValue : (int)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(Int32 rawValue, out Int32 tagValue)
        {
            tagValue = (Int32)Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    tagValue = (Int32)(rawValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    Int32 result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                case TagConvertType.BCD:
                    tagValue = rawValue.ToString("X").ObjToInt();
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(Int32 tagValue, out Int32 rawValue)
        {
            Cast(RawValue, out rawValue);

            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (Int32)(tagValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    Int32 result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                case TagConvertType.BCD:
                    rawValue = tagValue.ToString("X").ObjToInt();
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class UIntTag : IOTag<uint>
    {
        public UIntTag(DeviceAddress addr, IGroup group) : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(uint value)
        {
            return _group.WriteUInt32(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override uint GetValue()
        {
            return _value;
        }

        public override uint GetWriteValue()
        {
            return _writeValue;
        }

        public override uint LimitValue()
        {
            var max = Maximum > uint.MaxValue ? uint.MaxValue : (uint)Maximum;
            var min = Minimum < uint.MinValue ? uint.MinValue : (uint)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(UInt32 rawValue, out UInt32 tagValue)
        {
            tagValue = (UInt32)Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    tagValue = (UInt32)(rawValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    UInt32 result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                case TagConvertType.BCD:
                    tagValue = rawValue.ToString("X").ObjToUint();
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(UInt32 tagValue, out UInt32 rawValue)
        {
            Cast(RawValue, out rawValue);
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (UInt32)(tagValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    UInt32 result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                case TagConvertType.BCD:
                    rawValue = tagValue.ToString("X").ObjToUint();
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class FloatTag : IOTag<float>
    {
        public FloatTag(DeviceAddress addr, IGroup group) : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(float value)
        {
            return _group.WriteFloat(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override float GetValue()
        {
            return _value;
        }

        public override float GetWriteValue()
        {
            return _writeValue;
        }

        public override float LimitValue()
        {
            var max = Maximum > float.MaxValue ? float.MaxValue : (float)Maximum;
            var min = Minimum < float.MinValue ? float.MinValue : (float)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(float rawValue, out float tagValue)
        {
            tagValue = (float)Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                case TagConvertType.BCD:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    tagValue = (float)(rawValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    float result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(float tagValue, out float rawValue)
        {
            Cast(RawValue, out rawValue);

            //engineerValue = (EngineerValue != null) ? (float)EngineerValue : (float)0.0;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (float)(tagValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    float result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class StringTag : IOTag<string>
    {
        public StringTag(DeviceAddress addr, IGroup group)
            : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(string value)
        {
            return _group.WriteString(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value ?? string.Empty;
        }

        public override string GetValue()
        {
            return _value;
        }

        public override string GetWriteValue()
        {
            return _writeValue;
        }

        public override string LimitValue()
        {
            return _value;
        }

        public override bool SetValue(string engineerValue, out string tagValue)
        {
            tagValue = Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                case TagConvertType.BCD:
                    tagValue = engineerValue;
                    break;

                case TagConvertType.Expression:
                    string result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = engineerValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                default:
                    tagValue = engineerValue;
                    break;
            }
            return true;
        }

        public override bool SetRawValue(string tagValue, out string engineerValue)
        {
            Cast(RawValue, out engineerValue);

            //engineerValue = (EngineerValue != null) ? (UInt16)EngineerValue : (UInt16)0;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    engineerValue = tagValue;
                    break;

                case TagConvertType.Expression:
                    string result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    engineerValue = result;
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class LongTag : IOTag<long>
    {
        public LongTag(DeviceAddress addr, IGroup group) : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(long value)
        {
            return _group.WriteLong(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override long GetValue()
        {
            return _value;
        }

        public override long GetWriteValue()
        {
            return _writeValue;
        }

        public override long LimitValue()
        {
            var max = Maximum > long.MaxValue ? long.MaxValue : (long)Maximum;
            var min = Minimum < long.MinValue ? long.MinValue : (long)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(long rawValue, out long tagValue)
        {
            tagValue = (long)Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    tagValue = (long)(rawValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    long result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                case TagConvertType.BCD:
                    tagValue = rawValue.ToString("X").ObjToLong();
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(long tagValue, out long rawValue)
        {
            Cast(RawValue, out rawValue);
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (long)(tagValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    long result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                case TagConvertType.BCD:
                    rawValue = tagValue.ToString("X").ObjToLong();
                    break;

                default:
                    return false;
            }
            return true;
        }
    }

    public sealed class DoubleTag : IOTag<double>
    {
        public DoubleTag(DeviceAddress addr, IGroup group) : base(addr, group)
        {
        }

        #region IDevice Members

        public override int Write(double value)
        {
            return _group.WriteDouble(_plcAddress, value);
        }

        #endregion IDevice Members

        public override string ToString()
        {
            return _value.ToString();
        }

        public override double GetValue()
        {
            return _value;
        }

        public override double GetWriteValue()
        {
            return _writeValue;
        }

        public override double LimitValue()
        {
            var max = Maximum > double.MaxValue ? double.MaxValue : (double)Maximum;
            var min = Minimum < double.MinValue ? double.MinValue : (double)Minimum;
            if (_value > max) return max;
            if (_value < min) return min;
            return _value;
        }

        public override bool SetValue(double rawValue, out double tagValue)
        {
            tagValue = (double)Value;
            switch (ConverterType)
            {
                case TagConvertType.None:
                    tagValue = rawValue;
                    break;

                case TagConvertType.Scale:
                    tagValue = (double)(rawValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    double result;
                    if (string.IsNullOrEmpty(Script) || TagType != TagType.Normal) result = rawValue;
                    else
                    {
                        if (!ExecuteExpression(Script, out result))
                        {
                            return false;
                        }
                    }
                    tagValue = result;
                    break;

                case TagConvertType.BCD:
                    tagValue = rawValue.ToString("X").ObjToDouble();
                    break;

                default:
                    return false;
            }
            return true;
        }

        public override bool SetRawValue(double tagValue, out double rawValue)
        {
            Cast(tagValue, out rawValue);
            switch (ConverterType)
            {
                case TagConvertType.None:
                    rawValue = tagValue;
                    break;

                case TagConvertType.Scale:
                    rawValue = (double)(tagValue * ScaleValue);
                    break;

                case TagConvertType.Expression:
                    double result;
                    if (string.IsNullOrEmpty(WriteScript) || TagType != TagType.Normal) result = tagValue;
                    else
                    {
                        if (!ExecuteExpression(WriteScript, out result))
                        {
                            return false;
                        }
                    }
                    rawValue = result;
                    break;

                case TagConvertType.BCD:
                    rawValue = tagValue.ToString("X").ObjToDouble();
                    break;

                default:
                    return false;
            }
            return true;
        }
    }
}